home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / chrome / content / foxmarks-password.js < prev    next >
Text File  |  2010-01-28  |  30KB  |  812 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-password.js: component that implements the details of 
  5.  password sync.
  6.  
  7.  */
  8.  
  9.  
  10. function NiceProcess(iterator, funcDo, funcDone){
  11.     var callback = {
  12.         notify: function(timer){
  13.             try {
  14.                 var s = Date.now();
  15.                 while (!iterator.done() && Date.now() - s < 100) {
  16.                     funcDo(iterator);
  17.                 }
  18.                 if(iterator.done()){
  19.                     // dump("ms: " + (Date.now() - s) + "\n");
  20.                         funcDone(iterator);
  21.                 } else {
  22.                     // dump("ms: " + (Date.now() - s) + "\n");
  23.                         timer.initWithCallback(callback, 10,
  24.                             Ci.nsITimer.TYPE_ONE_SHOT);
  25.                 }
  26.             }catch(e){
  27.                 funcDone(iterator, e);
  28.             }
  29.         }
  30.     };
  31.     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  32.     timer.initWithCallback(callback, 10,
  33.         Ci.nsITimer.TYPE_ONE_SHOT);
  34. }
  35.  
  36. function PasswordDatasource(){
  37.     this.Cc = Components.classes;
  38.     this.Ci = Components.interfaces;
  39.  
  40.     this.JSON = this.Cc["@mozilla.org/dom/json;1"].createInstance(this.Ci.nsIJSON);
  41.  
  42.     this.encryptor = CreateAESManager();
  43. }
  44.  
  45. PasswordDatasource.prototype = {
  46.     DiscontinuityPrompt: function() {
  47.         var sb = Xmarks.Bundle().GetStringFromName;
  48.  
  49.         // get a reference to the prompt service component.
  50.         var ps = this.Cc["@mozilla.org/embedcomp/prompt-service;1"].
  51.           getService(this.Ci.nsIPromptService);
  52.  
  53.         var flags = ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0 +
  54.               ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 +
  55.               ps.BUTTON_TITLE_CANCEL    * ps.BUTTON_POS_2;
  56.  
  57.         rv = ps.confirmEx(null, sb("passworddisc.title"), sb("passworddisc.body"), flags,
  58.             sb("disc.merge"), sb("disc.download"), null, null, {});
  59.  
  60.         return rv;
  61.     },
  62.     // 2 - server, 1 -local, other-cancel
  63.     _conflictDialog: function(dest, src){
  64.         var retval = { button: 0 };
  65.         var data = {
  66.             local: { 
  67.                 site: src.hostname,
  68.                 username: src.username,
  69.                 password: src.password
  70.             },
  71.             server: {
  72.                 site: dest.hostname,
  73.                 username: dest.username,
  74.                 password: dest.password
  75.             }
  76.         };
  77.         var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
  78.             getService(Ci.nsIWindowMediator).
  79.             getMostRecentWindow(null);
  80.         
  81.         if (!topwin) {
  82.             Xmarks.LogWrite("conflictDialog: Couldn't find a topwin!");
  83.             throw 4;
  84.         }
  85.         var win = topwin.openDialog(
  86.             "chrome://foxmarks/content/foxmarks-passwordconflict.xul", "_blank",
  87.             "chrome,dialog,modal,centerscreen", data, retval);
  88.         if(!retval.button) {
  89.             throw 2;
  90.         }
  91.        
  92.        return retval.button;
  93.     },
  94.     ClobberDialog: function(len){
  95.         
  96.         if(len > 1)
  97.             return true;
  98.         var sb = Xmarks.Bundle().GetStringFromName;
  99.  
  100.         // get a reference to the prompt service component.
  101.         var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  102.           getService(Ci.nsIPromptService);
  103.  
  104.         var flags = ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_0 +
  105.               ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 +
  106.               ps.BUTTON_TITLE_IS_STRING    * ps.BUTTON_POS_2;
  107.  
  108.         rv = ps.confirmEx(null, sb("pwclobber.title"), sb("pwclobber.body"), flags,
  109.             null, sb("pwclobber.disable"), sb("pwclobber.server"),  null, {});
  110.  
  111.         if(rv == 1){
  112.             Xmarks.gSettings.setSyncEnabled("passwords", false);
  113.         }
  114.         return rv == 2;
  115.     },
  116.     syncType: "passwords",
  117.     getBaselineName: function(){
  118.         return "xmarks-password-baseline-" + Xmarks.gSettings.hash + "json";
  119.     },
  120.     BaselineLoaded: function(baseline, callback) {
  121.         var self = this;
  122.         baseline.OnTree(
  123.             function(nid){
  124.                 self.decode(baseline.Node(nid, true, false));
  125.             },
  126.             function(){
  127.                 callback(0);
  128.             }
  129.         );
  130.     },
  131.     encrypt: function(str){
  132.         var result = this.encryptor.encrypt(Xmarks.gSettings.pin, str);
  133.         if(Xmarks.gSettings.forceGC) {
  134.             Components.utils.forceGC();
  135.         }
  136.         return result;
  137.     },
  138.     decrypt: function(str){
  139.         var result =  this.encryptor.decrypt(Xmarks.gSettings.pin, str);
  140.         var goodPIN = result.indexOf('password') >=  0;
  141.  
  142.         // If we're doing a merge from a reset pin, there will be
  143.         // two pins floating around
  144.         if(!goodPIN && this._oldpin !== undefined){
  145.             result =  this.encryptor.decrypt(this._oldpin, str);
  146.             goodPIN = result.indexOf('password') >=  0;
  147.         }
  148.  
  149.         while(!goodPIN){
  150.             var newpin = this.getValidPin();
  151.             Xmarks.gSettings.pin = newpin;
  152.             result =  this.encryptor.decrypt(Xmarks.gSettings.pin, str);
  153.             goodPIN = result.indexOf('password') >=  0;
  154.             if(!goodPIN){
  155.                 Xmarks.Alert(Xmarks.Bundle().GetStringFromName("error.pinInvalid"));
  156.             }
  157.         }
  158.         return result;
  159.     },
  160.     getValidPin: function(){
  161.         var retval = { okay: false, pin: "" };
  162.         var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
  163.             getService(Ci.nsIWindowMediator).
  164.             getMostRecentWindow(null);
  165.         
  166.         if (!topwin) {
  167.             Xmarks.LogWrite("getValidPin: Couldn't find a topwin!");
  168.             throw 4;
  169.         }
  170.         var win = topwin.openDialog(
  171.             "chrome://foxmarks/content/foxmarks-invalidpin.xul", "_blank",
  172.             "chrome,dialog,modal,centerscreen", retval);
  173.         if(!retval.okay) {
  174.             Xmarks.gSettings.setSyncEnabled("passwords", false);
  175.             throw 2;
  176.         }
  177.  
  178.         // WILL- This looks like a mozilla bug, it deallocs result.pin
  179.         //  apparently so you need to assign a new string to it.
  180.         var s = "" + retval.pin;
  181.         return s;
  182.     },
  183.     generateNid: function(login){
  184.         return Xmarks.hex_md5("password|".concat(
  185.                     login.hostname, "|",
  186.                     login.formSubmitURL, "|",
  187.                     login.httpRealm, "|",
  188.                     login.usernameField, "|",
  189.                     login.passwordField, "|",
  190.                     login.username));
  191.     },
  192.     decode: function(node){
  193.         if(!node.private){
  194.             node.private = this.JSON.decode(this.decrypt(node.data));
  195.             if(Xmarks.gSettings.forceGC) {
  196.                 Components.utils.forceGC();
  197.             }
  198.         }
  199.         return node.private;
  200.     },
  201.     handleNidConflict: function(lnode, snode){
  202.         var litem = this.decode(lnode);
  203.         var sitem = this.decode(snode);
  204.         if(litem.password == sitem.password)
  205.             return "same";
  206.         else {
  207.             var result = this._conflictDialog(sitem, litem); 
  208.             switch(result){
  209.                 case 2:
  210.                     return "server";
  211.                 case 1:
  212.                     return "local";
  213.                 default:
  214.                     return "cancel";
  215.             }
  216.         }
  217.         return "cancel";;
  218.     },
  219.     _buildList: function(doEncrypt, includeCt, funcDone){
  220.         var mgr = this.Cc["@mozilla.org/login-manager;1"]
  221.             .getService(this.Ci.nsILoginManager);
  222.  
  223.         var a = mgr.getAllLogins({});
  224.         var ct = a.length;
  225.         var self = this;
  226.         var size = 0;
  227.         //dump("_buildList\n");
  228.         NiceProcess(
  229.             {  ctr: 0,
  230.                a: a,
  231.                len: a.length,
  232.                size: 0,
  233.                result: {},
  234.                done: function(){ return this.ctr >= this.len;}
  235.             },
  236.             function(iter){
  237.                 var login = iter.a[iter.ctr];
  238.                 if(login.httpRealm != Xmarks.SYNC_REALM &&
  239.                         login.httpRealm != Xmarks.SYNC_REALM_PIN &&
  240.                         login.httpRealm != Xmarks.FOXMARKS_SYNC_REALM &&
  241.                         login.httpRealm != Xmarks.FOXMARKS_SYNC_REALM_PIN){
  242.                     var nid = self.generateNid(login);
  243.                     iter.result[nid] =  {
  244.                         ntype: "password",
  245.                         hostname: login.hostname,
  246.                         formSubmitURL: login.formSubmitURL,
  247.                         httpRealm: login.httpRealm,
  248.                         username: login.username,
  249.                         usernameField: login.usernameField,
  250.                         password: login.password,
  251.                         passwordField: login.passwordField,
  252.                         pnid: NODE_ROOT
  253.                     };
  254.                     if(doEncrypt){
  255.                         iter.result[nid].blob = self.encrypt(
  256.                             iter.result[nid].toJSONString()
  257.                         );
  258.                     }
  259.                     iter.size++;
  260.                 }
  261.                 iter.ctr++;
  262.             },
  263.             function(iter, e){
  264.                 if(includeCt)
  265.                     iter.result.totalLogins = iter.size;
  266.                 funcDone(iter.result, e);
  267.             }
  268.         );
  269.     },
  270.  
  271.     ProvideNodes: function(Caller, AddNode, Complete) {
  272.         var self = this;
  273.         this._buildList(true, false, function(a, e){
  274.             // if buildlist threw an error, just drop out now
  275.             if(e) {
  276.                 Complete.apply(Caller, [e]);
  277.                 return;
  278.             }
  279.             try {
  280.                 var now = new Date();
  281.                 var random_stuff = {
  282.                     password: Xmarks.hex_md5(now.getTime().toString())
  283.                 };
  284.                 var root_node = new Node(
  285.                     NODE_ROOT, 
  286.                     {
  287.                         ntype: "folder", 
  288.                         children: [],
  289.                         private: random_stuff,
  290.                         data: self.encrypt(random_stuff.toJSONString())
  291.                     }
  292.                 );
  293.                 var nid;
  294.                 var obj = {};
  295.                 for(nid in a){
  296.                     if(obj[nid] === undefined){ 
  297.                         var item = a[nid];
  298.                         node = new Node(nid, {
  299.                             ntype: "password",
  300.                             data:  item.blob,
  301.                             private: item,
  302.                             pnid: NODE_ROOT
  303.                         });
  304.                         root_node.children.push(nid);
  305.                         AddNode.apply(Caller, [node]);
  306.                     }
  307.                 }
  308.                 AddNode.apply(Caller, [root_node]);
  309.  
  310.                 Complete.apply(Caller, [0]);
  311.            } catch(e) {
  312.                 if(typeof e != "number"){
  313.                     Components.utils.reportError(e);
  314.                     Complete.apply(Caller, [4]);
  315.                 } else {
  316.                     Complete.apply(Caller, [e]);
  317.                 }
  318.            }
  319.         });
  320.     },
  321.     AcceptNodes: function(ns, callback) {
  322.         var self = this;
  323.         this._buildList(false, false, function(list, e){
  324.             // if buildlist ran into an error, drop out now
  325.             if(e) {
  326.                 callback(e);
  327.                 return;
  328.             }
  329.             var nid, node;
  330.             var mgr = self.Cc["@mozilla.org/login-manager;1"]
  331.                 .getService(self.Ci.nsILoginManager);
  332.  
  333.             var removeList = [];
  334.             // Remove any nids that aren't in ns
  335.             for(nid in list){
  336.                 if(list.hasOwnProperty(nid)){
  337.                     node = ns.Node(nid, false, true);
  338.                     if(node === null){
  339.                         removeList.push(nid);
  340.                     }
  341.                 }
  342.             }
  343.  
  344.             //dump("AcceptNodes\n");
  345.             NiceProcess(
  346.                 {
  347.                     a: removeList,
  348.                     len: removeList.length,
  349.                     ctr: 0,
  350.                     done: function() { return this.ctr >= this.len; }
  351.                 },
  352.                 function(iter){
  353.                     var nid = iter.a[iter.ctr];
  354.                     var item = list[nid];
  355.                     var logins = mgr.findLogins({},
  356.                         item.hostname, 
  357.                         item.formSubmitURL, 
  358.                         item.httpRealm);
  359.                         
  360.                     for(var i = 0; i < logins.length; i++) {
  361.                         if(logins[i].username == item.username) {
  362.                             mgr.removeLogin(logins[i]);
  363.                             break;
  364.                         }
  365.                     }
  366.                     
  367.                     iter.ctr++;
  368.                 },
  369.                 function(oldIter, e){
  370.                     // If we had an error, just drop out now
  371.                     if(e) {
  372.                         callback(e);
  373.                         return;
  374.                     }
  375.                     var node = ns.Node(NODE_ROOT, false, true);
  376.  
  377.                     //dump("AcceptNodes 2\n");
  378.                     NiceProcess(
  379.                         {
  380.                             ctr: 0,
  381.                             children: (node.children || []),
  382.                             done: function() {
  383.                                 return this.ctr >= this.children.length;
  384.                             }
  385.                         },
  386.                         function(iter){
  387.                             var nid = iter.children[iter.ctr];
  388.  
  389.                             // Add it
  390.                             node = ns.Node(nid); 
  391.                             var  item = list[nid];
  392.                             var  loginInfo;
  393.  
  394.                             if(!item){
  395.                                 try {
  396.                                     var nitem = self.decode(node);
  397.                                     loginInfo = self.Cc[
  398.                                         "@mozilla.org/login-manager/loginInfo;1"].
  399.                                         createInstance(self.Ci.nsILoginInfo);
  400.                                     loginInfo.init(
  401.                                         nitem.hostname, nitem.formSubmitURL, 
  402.                                         nitem.httpRealm, 
  403.                                         nitem.username, 
  404.                                         nitem.password,
  405.                                         nitem.usernameField, 
  406.                                         nitem.passwordField);
  407.                                     mgr.addLogin(loginInfo);
  408.                                 }
  409.                                 catch(e){
  410.                                     // Pass cancels up the chain
  411.                                     if(typeof e == "number"){ 
  412.                                         throw e;
  413.                                     } else {
  414.                                         Components.utils.reportError(e);
  415.                                     }
  416.                                 }
  417.                             }
  418.                             // Check for changes
  419.                             else if(node.password != item.password){
  420.                                 var logins = mgr.findLogins({},
  421.                                         item.hostname, 
  422.                                         item.formSubmitURL, 
  423.                                         item.httpRealm);
  424.                                         
  425.                                 for(var i = 0; i < logins.length; i++) {
  426.                                     if(logins[i].username == item.username) {
  427.                                         var nitem = self.decode(node);
  428.                                         loginInfo = self.Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(self.Ci.nsILoginInfo);
  429.                                         loginInfo.init(
  430.                                             nitem.hostname,
  431.                                             nitem.formSubmitURL, 
  432.                                             nitem.httpRealm, 
  433.                                             nitem.username, 
  434.                                             nitem.password,
  435.                                             nitem.usernameField, 
  436.                                             nitem.passwordField
  437.                                         );
  438.                                         mgr.modifyLogin(logins[i], loginInfo);
  439.                                         break;
  440.                                     }
  441.                                 }
  442.                             }
  443.                             iter.ctr++;
  444.  
  445.                         },
  446.                         function(iter, e){
  447.                             e = e || 0;
  448.                             callback(e);
  449.                         }
  450.                     );
  451.                 }
  452.              );
  453.          });
  454.     },
  455.  
  456.     merge: function(dest, source) {
  457.         var snode =source.Node(NODE_ROOT, false, true); 
  458.         this._oldpin = Xmarks.gSettings.pin;
  459.         if(snode == null)
  460.             return;
  461.         var dnode = dest.Node(NODE_ROOT, false, true);
  462.         if(dnode == null){
  463.             var now = new Date();
  464.             var random_stuff = {
  465.                     password: Xmarks.hex_md5(now.getTime().toString())
  466.             };
  467.             dest.Do_insert(NODE_ROOT, {ntype: "folder", children: [],
  468.                 data: this.encrypt(random_stuff.toJSONString())});
  469.             dnode = dest.Node(NODE_ROOT, false, true);
  470.         }
  471.         if(!dnode.data){
  472.             var now = new Date();
  473.             var random_stuff = {
  474.                     password: Xmarks.hex_md5(now.getTime().toString())
  475.             };
  476.             ditem = dest.Node(NODE_ROOT,true);
  477.             delete ditem.private;
  478.             ditem.data = this.encrypt(random_stuff.toJSONString());
  479.         }
  480.         var dlist = dnode.children || [];
  481.         var slist = snode.children || [];
  482.  
  483.         var ctr = slist.length;
  484.  
  485.         while(ctr--){
  486.             var nid = slist[ctr];
  487.             var sitem = source.Node(nid);
  488.             var ditem = dest.Node(nid, false,true);
  489.             if(!ditem){
  490.                 dest.Do_insert(nid, sitem.GetSafeAttrs());
  491.             }
  492.             else {
  493.                 var sdata = this.decode(sitem);
  494.                 var ddata = this.decode(ditem);
  495.                 if(sdata.password != ddata.password){
  496.                     var cResult = this._conflictDialog(ddata, sdata);
  497.                     if(cResult  == 1){
  498.                         ditem = dest.Node(nid,true);
  499.                         delete ditem.private;
  500.                         ditem.data = sitem.data;
  501.                     }
  502.                 }
  503.             }
  504.         }
  505.         delete this._oldpin;
  506.     },
  507.     orderIsImportant: false,
  508.     compareNodes: function(snode, onode, attrs){
  509.         if(!snode.data && onode.data){
  510.             attrs['data'] = onode.data;
  511.             return true;
  512.         }
  513.         else if(!onode.data && snode.data){
  514.             attrs['data'] = snode.data;
  515.             return true;
  516.         }
  517.         else if(snode.data === undefined || onode.data === undefined){
  518.             return false;
  519.         }
  520.         var sdata = this.decode(snode);
  521.         var ddata = this.decode(onode);
  522.         attrs.data = onode.data;
  523.         return sdata.password != ddata.password && snode.nid != NODE_ROOT;
  524.     },
  525.     verifyPin: function(pin, node){
  526.         Xmarks.LogWrite("Verifying PIN (Testing Node)");
  527.         Xmarks.LogWrite("Node Stats: " + (node.data ? node.data.length : "null"));
  528.         var result =  this.encryptor.decrypt(pin, node.data);
  529.         Xmarks.LogWrite("Node Verification: " + (result.indexOf('password') >= 0 ?
  530.             "true" : "false"));
  531.         return result.indexOf('password') >= 0;
  532.     },
  533.     WatchForChanges: function(server) {
  534.         return new PasswordSyncWatcher(server);
  535.     }
  536. };
  537. function PasswordSyncWatcher(server){
  538.     this.server = server;
  539.     this.mgr = new PasswordDatasource();
  540.     this.start();
  541. }
  542.  
  543. PasswordSyncWatcher.prototype = {
  544.     start: function(){
  545.         var os = Cc["@mozilla.org/observer-service;1"].
  546.             getService(Ci.nsIObserverService);
  547.         os.addObserver(this, "foxmarks-checkforpasswordchange", false);
  548.         os.addObserver(this, "foxmarks-watchersuspend", false);
  549.     },
  550.     _suspended: false,
  551.  
  552.     /*
  553.         I took this code from nsLoginManagerPrompter.js
  554.         to make sure we watch the same notify box that the
  555.         loginmanager does.
  556.     */
  557.    _getNotifyBox : function (win) {
  558.        try {
  559.            // Get topmost window, in case we're in a frame.
  560.            var notifyWindow = win.top;
  561.  
  562.            // Some sites pop up a temporary login window, when disappears
  563.            // upon submission of credentials. We want to put the notification
  564.            // bar in the opener window if this seems to be happening.
  565.            if (notifyWindow.opener) {
  566.                var webnav = notifyWindow
  567.                                    .QueryInterface(Ci.nsIInterfaceRequestor)
  568.                                    .getInterface(Ci.nsIWebNavigation);
  569.                var chromeWin = webnav
  570.                                    .QueryInterface(Ci.nsIDocShellTreeItem)
  571.                                    .rootTreeItem
  572.                                    .QueryInterface(Ci.nsIInterfaceRequestor)
  573.                                    .getInterface(Ci.nsIDOMWindow);
  574.                var chromeDoc = chromeWin.document.documentElement;
  575.  
  576.                // Check to see if the current window was opened with chrome
  577.                // disabled, and if so use the opener window. But if the window
  578.                // has been used to visit other pages (ie, has a history),
  579.                // assume it'll stick around and *don't* use the opener.
  580.                if (chromeDoc.getAttribute("chromehidden") &&
  581.                    webnav.sessionHistory.count == 1) {
  582.                    notifyWindow = notifyWindow.opener;
  583.                }
  584.            }
  585.  
  586.  
  587.            // Find the <browser> which contains notifyWindow, by looking
  588.            // through all the open windows and all the <browsers> in each.
  589.            var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  590.                     getService(Ci.nsIWindowMediator);
  591.            var enumerator = wm.getEnumerator("navigator:browser");
  592.            var tabbrowser = null;
  593.            var foundBrowser = null;
  594.  
  595.            while (!foundBrowser && enumerator.hasMoreElements()) {
  596.                var win = enumerator.getNext();
  597.                tabbrowser = win.getBrowser(); 
  598.                foundBrowser = tabbrowser.getBrowserForDocument(
  599.                                                  notifyWindow.document);
  600.            }
  601.  
  602.            // Return the notificationBox associated with the browser.
  603.            // Apparently sometimes the notify box doesn't have this
  604.            // function so we should test for it. Bug #4122
  605.            if (foundBrowser){
  606.                var box = tabbrowser.getNotificationBox(foundBrowser);
  607.                return (box && box.getNotificationWithValue) ? box : null;
  608.            }
  609.  
  610.        } catch (e) {
  611.            // If any errors happen, just assume no notification box.
  612.        }
  613.  
  614.        return null;
  615.    },  
  616.     formsubmit : function (form) {
  617.         var doc = form.ownerDocument;
  618.         var win = doc.defaultView;
  619.         var self = this;
  620.  
  621.         // check if there is a password field
  622.         var ct = form.elements.length;
  623.         var found = false;
  624.         while(ct--){
  625.             if(form.elements[ct].type == "password"){
  626.                 found = true;
  627.                 break;
  628.             }
  629.         }
  630.         if(found){
  631.             this.monitorForChange(win);
  632.         }
  633.         return true;
  634.     },
  635.  
  636.     scheduleChangeTest: function(wait){
  637.         var self = this;
  638.         var callbackSingle = {
  639.             notify: function(){
  640.                 try {
  641.                     self.checkForChanges();
  642.                 } catch(e){
  643.                     Xmarks.LogWrite("PwdM Error: " + e.message + "(" +
  644.                             e.fileName + ": " + e.lineNumber + ")");
  645.                 }
  646.             }
  647.         };
  648.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  649.         timer.initWithCallback(callbackSingle, wait,
  650.             Ci.nsITimer.TYPE_ONE_SHOT);
  651.     },
  652.     monitorForClose: function(box,type){
  653.         var self = this;
  654.         var timerRepeating;
  655.         var maxCtr = 20;
  656.  
  657.         var callbackWait = {
  658.             notify: function(){
  659.                 if(!box || (box && !box.getNotificationWithValue)){
  660.                     timerRepeating.cancel();
  661.                 } else if(box.getNotificationWithValue(type) == null){
  662.                     try {
  663.                         self.checkForChanges();
  664.                     } catch(e){
  665.                         Xmarks.LogWrite("PwdM Error: " + e.message + "(" +
  666.                                 e.fileName + ": " + e.lineNumber + ")");
  667.                     }
  668.                     timerRepeating.cancel();
  669.                     }
  670.                 else if(maxCtr-- == 0)
  671.                     timerRepeating.cancel();
  672.             }
  673.         };
  674.         timerRepeating = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  675.         timerRepeating .initWithCallback(callbackWait, 1000,
  676.             Ci.nsITimer.TYPE_REPEATING_SLACK);
  677.     },
  678.     checkNotifyBox: function(){
  679.         var notifyType = "";
  680.         var self = this;
  681.  
  682.         var box = self._getNotifyBox(win);
  683.         if(box && box.getNotificationWithValue){
  684.             notifyType = "password-save";
  685.             if(box.getNotificationWithValue(notifyType) == null){
  686.                 notifyType = "password-change";
  687.                 if(box.getNotificationWithValue(notifyType) == null){
  688.                     notifyType = "";
  689.                 }
  690.             }
  691.         }
  692.  
  693.         return notifyType;
  694.     },
  695.     monitorForChange: function(win){
  696.         var self = this;
  697.         var numTimes = 0;
  698.         var callbackFirst = {
  699.             notify: function(){
  700.                 /* Check if we have a notification.
  701.                     if so, poll it until it closes
  702.                 */
  703.                 numTimes++;
  704.                 var box = self._getNotifyBox(win);
  705.                 var secondTry = false;
  706.                 if(box && box.getNotificationWithValue){
  707.                     var notifyType = "password-save";
  708.                     if(box.getNotificationWithValue(notifyType) == null){
  709.                         notifyType = "password-change";
  710.                         if(box.getNotificationWithValue(notifyType) == null){
  711.                             notifyType = "";
  712.                         }
  713.                     }
  714.                     if(notifyType != ""){
  715.                         self.monitorForClose(box, notifyType);
  716.                     }
  717.                     else
  718.                         self.scheduleChangeTest(8000);
  719.                 }
  720.                 // there are times that the loginManager
  721.                 //  will put up a dialog instead of a notification
  722.                 //  box.  In this case, and when they change a password,
  723.                 //  we'll wait 10 seconds more and then check for changes
  724.                 else {
  725.                     if(numTimes > 4){
  726.                         self.scheduleChangeTest(8000);
  727.                     }
  728.                     else {
  729.                         timer.initWithCallback(callbackFirst, 500,
  730.                             Ci.nsITimer.TYPE_ONE_SHOT);
  731.                     }
  732.                 }
  733.             }
  734.         };
  735.  
  736.         // let things settle for a second and see what happens
  737.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  738.         timer.initWithCallback(callbackFirst, 500,
  739.             Ci.nsITimer.TYPE_ONE_SHOT);
  740.     },
  741.     observe: function(subject, topic, data)  { 
  742.         var self = this;
  743.  
  744.         switch(topic){
  745.             case "foxmarks-checkforpasswordchange":
  746.                 Xmarks.LogWrite("Checking for password changes.");
  747.                 this.checkForChanges();
  748.                 return;
  749.             case "foxmarks-watchersuspend":
  750.                 this._suspended = data == "1";
  751.                 return;
  752.         }
  753.     },
  754.     checkForChanges: function(){
  755.         var self = this;
  756.         if(!Xmarks.gSettings.isSyncEnabled("passwords") || !Xmarks.gSettings.haveSynced || self._suspended)
  757.             return;
  758.  
  759.         var funcNotifyChange = function(data){
  760.             var lm = Date.now();
  761.             if (!self.lastModified || lm > self.lastModified && !self._suspended) {
  762.                 self.lastModified = lm;
  763.                 var os = Cc["@mozilla.org/observer-service;1"]
  764.                     .getService(Ci.nsIObserverService);
  765.                 os.notifyObservers(null, "foxmarks-datasourcechanged", 
  766.                     lm + ";passwords");
  767.             }
  768.         };
  769.         this.server.getBaseline("passwords", function(status, ns){
  770.             if(status)
  771.                 return;
  772.             
  773.             var mydata = {
  774.                 totalLogins: 0
  775.             };
  776.             var funcCompareList = function(data, e){
  777.                 // if buildlist returned an error, just drop out silently and hope for the best
  778.                 if(e){
  779.                     return;
  780.                 }
  781.  
  782.                 if(mydata.totalLogins != data.totalLogins){
  783.                     funcNotifyChange(data);
  784.                     return;
  785.                 }
  786.                 for(var field in data){
  787.                     if(data.hasOwnProperty(field)){
  788.                         if(!mydata.hasOwnProperty(field) ||
  789.                             mydata[field].password != data[field].password){
  790.                             funcNotifyChange(data);
  791.                             return;
  792.                         }
  793.                     }
  794.                 }
  795.             };
  796.             ns.OnTree(
  797.                 function(nid){
  798.                     if(nid != NODE_ROOT){
  799.                         mydata.totalLogins++;
  800.                         mydata[nid] = ns.Node(nid).private;
  801.                     }
  802.                 },
  803.                 function(){
  804.                     self.mgr._buildList(false, true, funcCompareList);
  805.                 }
  806.             );
  807.  
  808.         });
  809.  
  810.     }
  811. };
  812.